package vip.xiaonuo.biz.modular.timetable.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fhs.trans.service.impl.TransService;
import org.springframework.stereotype.Service;
import vip.xiaonuo.biz.modular.klass.entity.EduKlass;
import vip.xiaonuo.biz.modular.klass.service.EduKlassService;
import vip.xiaonuo.biz.modular.lecture.entity.EduLecture;
import vip.xiaonuo.biz.modular.lecture.enums.EduLectureStateEnum;
import vip.xiaonuo.biz.modular.lecture.service.EduLectureService;
import vip.xiaonuo.biz.modular.lesson.entity.EduLesson;
import vip.xiaonuo.biz.modular.lesson.service.EduLessonService;
import vip.xiaonuo.biz.modular.room.entity.EduRoom;
import vip.xiaonuo.biz.modular.room.service.EduRoomService;
import vip.xiaonuo.biz.modular.teacher.entity.EduTeacher;
import vip.xiaonuo.biz.modular.teacher.service.EduTeacherService;
import vip.xiaonuo.biz.modular.timeslot.entity.EduTimeslot;
import vip.xiaonuo.biz.modular.timeslot.service.EduTimeslotService;
import vip.xiaonuo.biz.modular.timetable.algorithm.GeneticAlgorithm;
import vip.xiaonuo.biz.modular.timetable.algorithm.Population;
import vip.xiaonuo.biz.modular.timetable.domain.Lecture;
import vip.xiaonuo.biz.modular.timetable.domain.Lesson;
import vip.xiaonuo.biz.modular.timetable.domain.Timetable;
import vip.xiaonuo.biz.modular.tutorial.service.EduTutorialService;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * Don't be daunted by the number of classes in this chapter -- most of them are
 * just simple containers for information, and only have a handful of properties
 * with setters and getters.
 * 
 * The real stuff happens in the GeneticAlgorithm class and the Timetable class.
 * 
 * The Timetable class is what the genetic algorithm is expected to create a
 * valid version of -- meaning, after all is said and done, a chromosome is read
 * into a Timetable class, and the Timetable class creates a nicer, neater
 * representation of the chromosome by turning it into a proper list of Classes
 * with rooms and professors and whatnot.
 * 
 * The Timetable class also understands the problem's Hard Constraints (ie, a
 * professor can't be in two places simultaneously, or a room can't be used by
 * two classes simultaneously), and so is used by the GeneticAlgorithm's
 * calcFitness class as well.
 * 
 * Finally, we overload the Timetable class by entrusting it with the
 * "database information" generated here in initializeTimetable. Normally, that
 * information about what professors are employed and which classrooms the
 * university has would come from a database, but this isn't a book about
 * databases so we hardcode it.
 * 
 * @author bkanber
 *
 */
@Service
public class TimetableService {

	@Resource
	private EduRoomService eduRoomService;

	@Resource
	private EduTimeslotService eduTimeslotService;

	@Resource
	private EduTeacherService eduTeacherService;

	@Resource
	private EduKlassService eduKlassService;

	@Resource
	private EduLectureService eduLectureService;

	@Resource
	private EduTutorialService eduTutorialService;

	@Resource
	private EduLessonService eduLessonService;

	@Resource
	private TransService transService;

	public int detect(String lessonIds) {
		try {
			// Get a Timetable object with all the available information.
			Timetable timetable = initializeTimetable(StrUtil.EMPTY, lessonIds);

			int numberOfClash = timetable.calcClashes();
			System.out.print("number of clash: " + numberOfClash);
			return numberOfClash;
		}
		catch (Exception e) {
			e.printStackTrace();
			return -1;
		}
	}

    public void process(String lectureIds) {
    	// Get a Timetable object with all the available information.
        Timetable timetable = initializeTimetable(lectureIds, StrUtil.EMPTY);
        
        // Initialize GA
        GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.01, 0.9, 2, 5);
        
        // Initialize population
        Population population = ga.initPopulation(timetable);
        
        // Evaluate population
        ga.evalPopulation(population, timetable);
        
        // Keep track of current generation
        int generation = 1;
        
        // Start evolution loop
        while (ga.isTerminationConditionMet(generation, 1000) == false
            && ga.isTerminationConditionMet(population) == false) {
            // Print fitness
            System.out.println("G" + generation + " Best fitness: " + population.getFittest(0).getFitness());

            // Apply crossover
            population = ga.crossoverPopulation(population);

            // Apply mutation
            population = ga.mutatePopulation(population, timetable);

            // Evaluate population
            ga.evalPopulation(population, timetable);

            // Increment the current generation
            generation++;
        }

        // Print fitness
        timetable.createLessons(population.getFittest(0));
        System.out.println();
        System.out.println("Solution found in " + generation + " generations");
        System.out.println("Final solution fitness: " + population.getFittest(0).getFitness());
        System.out.println("Clashes: " + timetable.calcClashes());

        // Print classes
        System.out.println();
        Lesson lessons[] = timetable.getLessons();
        int classIndex = 1;
        for (Lesson bestLesson : lessons) {
            System.out.println("Class " + classIndex + ":");
            System.out.println("Module: " + 
                    timetable.getModule(bestLesson.getModuleId()).getModuleName());
            System.out.println("Group: " +
					timetable.getLecture(bestLesson.getLectureId()).getLectureId());
            System.out.println("Room: " + 
                    timetable.getRoom(bestLesson.getRoomId()).getRoomNumber());
            System.out.println("Professor: " + 
                    timetable.getProfessor(bestLesson.getProfessorId()).getProfessorName());
            System.out.println("Time: " + 
                    timetable.getTimeslot(bestLesson.getTimeslotId()).getTimeslot());
            System.out.println("-----");
            classIndex++;
        }
		finalizeTimetable(timetable);
    }

	/**
     * Creates a Timetable with all the necessary course information.
     * 
     * Normally you'd get this info from a database.
     * 
     * @return
     */
	private Timetable initializeTimetable(String lectureIds, String lessonIds) {
		// Create timetable
		Timetable timetable = new Timetable();

		// Set up rooms
		List<EduRoom> rooms = eduRoomService.list();
		for (EduRoom room: rooms) {
			timetable.addRoom(String.valueOf(room.getId()), room.getName(), room.getCapacity(), room.getType());
		}

		// Set up timeslots
		List<EduTimeslot> timeslots = eduTimeslotService.list();
		for (EduTimeslot timeslot: timeslots) {
			timetable.addTimeslot(String.valueOf(timeslot.getId()), timeslot.getName(), timeslot.getCode());
		}

		// Set up professors
		List<EduTeacher> teachers = eduTeacherService.list();
		for (EduTeacher teacher: teachers) {
			timetable.addProfessor(teacher.getId(), teacher.getName());
		}

		// Set up student class
		List<EduKlass> klasses = eduKlassService.list();
		for (EduKlass klass: klasses) {
			timetable.addKlass(klass.getId(), klass.getName());
		}

		List<EduLecture> lectures = eduLectureService.listByIds(StrUtil.split(lectureIds, StrUtil.COMMA));
		lectures.addAll(eduLectureService.listScheduled());
		for (EduLecture lecture: lectures) {
			transService.transOne(lecture);
			// Set up modules and define the professors that teach them
			int timesPerWeek = (lecture.getClassHour() / (lecture.getToWeek() - lecture.getFromWeek() + 1))/2;
			timetable.addModule(lecture.getCourseId(), lecture.getCourseName(), timesPerWeek, new String[] { lecture.getTeacherId() });
			// Set up student groups and the modules they take.
			String[] klassIds = JSONUtil.parseArray(lecture.getKlasses()).toArray(new String[]{});
			List<String> moduleIds = new ArrayList<>();
			moduleIds.add(lecture.getCourseId());
			timetable.addLecture(lecture.getId(), lecture.getState(), lecture.getSize(), lecture.getFromWeek(), lecture.getToWeek(), lecture.getRoomType(), 2, klassIds, moduleIds.toArray(new String[moduleIds.size()]));
		}

		// Get all scheduled lessons, assign into timetable
		QueryWrapper<EduLesson> queryWrapper = new QueryWrapper<>();
		if (ObjectUtil.isNotEmpty(lessonIds)) {
			queryWrapper.lambda().in(EduLesson::getId, StrUtil.split(lessonIds, StrUtil.COMMA));
		}
		List<EduLesson> lessons = eduLessonService.list(queryWrapper);
		for (EduLesson lesson: lessons) {
			EduLecture lecture = eduLectureService.getById(lesson.getLectureId());
			String[] klassIds = JSONUtil.parseArray(lecture.getKlasses()).toArray(new String[]{});
			timetable.addScheduledLesson(lesson.getId().toString(), lesson.getLectureId(), lesson.getCourseId(), lecture.getTeacherId(), String.valueOf(lesson.getTimeslotId()), String.valueOf(lesson.getRoomId()), klassIds);
		}
		return timetable;
	}

	private void finalizeTimetable(Timetable timetable) {
		Lesson lessons[] = timetable.getLessons();
		for (Lesson bestLesson : lessons) {
			EduLesson lesson = new EduLesson();
			lesson.setRoomId(Long.parseLong(bestLesson.getRoomId()));
			lesson.setTimeslotId(Long.parseLong(bestLesson.getTimeslotId()));
			lesson.setLectureId(bestLesson.getLectureId());
			eduLessonService.save(lesson);
			System.out.println("---------------");
		}
		Lecture lectures[] = timetable.getSchedulingLecturesAsArray();
		for (Lecture lecture: lectures) {
			EduLecture eduLecture = eduLectureService.getById(lecture.getLectureId());
			eduLecture.setState(EduLectureStateEnum.DONE.getValue());
			eduLectureService.updateById(eduLecture);
		}
	}
}
